/**
 * 
 * The Player class represents a player object in the game. The player class holds data such as
 * the player's position, the player's speed, etc. It also moves the player in 4 directions.
 * 
 * Author: Matthias Van Eeghem <matthias@van-eeghem.com>
 * All rights reserved.
 * 
 */

package com.bomberman.player;

import java.util.ArrayList;

import com.bomberman.core.Element;
import com.bomberman.core.Game;
import com.bomberman.explosion.Explosion;
import com.bomberman.explosion.ExplosionManager;

public class Player extends Element
{
	/**
	 * The player's speed (speed represents the amount of pixels the player is moved in any direction)
	 */
	private int speed;
	/**
	 * Holds the player name
	 */
	private String name;
	/**
	 * Hold the player id
	 */
	private int id;
	
	/**
	 * Defines all directions the player can move in
	 */
	enum MoveDirections
	{
		DirectionUp,
		DirectionDown,
		DirectionLeft,
		DirectionRight
	}
	
	/**
	 * The constructor of the player class, initialises all variables.
	 * 
	 * @param game
	 */
	public Player(String name, int row, int column, int id)
	{
		super(row, column, Element.ElementType.TYPE_PLAYER);

		// Set the player's speed
		this.speed = Game.PLAYER_DEFAULT_SPEED;
		// Set the name
		this.name = name;
		// Set the ID
		this.id = id;
	}
	
	/**
	 * Returns the player id
	 * 
	 * @return
	 */
	public int getID()
	{
		return id;
	}
	
	/**
	 * Returns the player's name
	 * 
	 * @return Player's name as string
	 */
	public String getName()
	{
		return name;
	}
	
	/**
	 * Check if the player moved into an explosion
	 * If so, kill them
	 */
	public void checkIfPlayerMovedIntoExplosion()
	{
		// Get the column or columns the player is located on
		int firstColumn = this.getColumns().get(0);
		int secondColumn = (this.getColumns().size() > 1) ? this.getColumns().get(1) : Game.INVALID_COLUMN_ID;
		
		// Get the row or rows the player is located on
		int firstRow = this.getRows().get(0);
		int secondRow = (this.getRows().size() > 1) ? this.getRows().get(1) : Game.INVALID_ROW_ID;
		
		Player killer = null;
		
		// Loop through all the elements
		for(Explosion explosion : ExplosionManager.getExplosionList())
		{
			int row = explosion.getRow();
			int column = explosion.getColumn();
			
			killer = explosion.getBombOwner();
			
			// The player is at the exact row and column
			if(firstRow == row && firstColumn == column
			// The first row is a match and the column matches the secondColumn
			|| firstRow == row && (secondRow != Game.INVALID_ROW_ID && secondColumn == column)			
			// The second row is a match and the column matches the firstColumn
			|| (secondRow != Game.INVALID_ROW_ID && secondRow == row) && firstColumn == column
			// secondRow matches the first row and the second column is the column
			|| (secondRow != Game.INVALID_ROW_ID && secondRow == row) && (secondColumn != Game.INVALID_COLUMN_ID && secondColumn == column))
			{
				Game.gameOver = true;
				break;
			}	
		}
		
		if(Game.gameOver)
		{
			System.out.printf("%s was killed by %s\n", this.getName(), killer.getName());
		}
	}
	
	/**
	 * This method moves the player PLAYER_SPEED pixels upwards
	 */
	public void moveUp()
	{
		// Get the column or columns the player is located on
		int firstColumn = this.getColumns().get(0);
		int secondColumn = (this.getColumns().size() > 1) ? this.getColumns().get(1) : Game.INVALID_COLUMN_ID;
		
		// The player is trying to exit the play area
		if((this.maximumYCoordinate - speed) <= Game.FIRSTBRICK_MAX_Y_COORDINATE)
		{
			// Make sure there's no brick between the edge of the field and the player
			// He's only on one column
			if(secondColumn == Game.INVALID_COLUMN_ID)
			{
				if(Element.isAnyElementAtPosition(0, firstColumn))
					return;
			}
			// He's on two columns
			else
			{
				if(Element.isAnyElementAtPosition(0, firstColumn) || Element.isAnyElementAtPosition(0, secondColumn))
					return;					
			}
			
			// No brick between the player and the edge, set the player at the edge
			this.maximumYCoordinate = Game.FIRSTBRICK_MAX_Y_COORDINATE - 1;
			this.minimumYCoordinate = Game.FIRSTBRICK_MAX_Y_COORDINATE + Game.BRICK_WIDTH - 1;
			
			checkIfPlayerMovedIntoExplosion();
			return;
		}
		// The player isn't trying to exit the play area
		else
		{
			// Calculate the position the player will be at once he has moved
			int minY = this.minimumYCoordinate - speed;
			int maxY = this.maximumYCoordinate - speed;
			
			// Loop through all the elements
			for(Element element : Element.getElementList())
			{
				if(element.getElementType() == Element.ElementType.TYPE_PLAYER)
					continue;
				
				if(element.getElementType() == Element.ElementType.TYPE_EXPLOSION)
					continue;
				
				// We only process the bricks that are on the same column or columns of the player
				if(element.getColumn() == firstColumn || (secondColumn != Game.INVALID_COLUMN_ID && element.getColumn() == secondColumn))
				{
					// Is the brick in the player's way?
					if(maxY <= element.getMinimumYCoordinate() && maxY >= element.getMaximumYCoordinate())
					{
						// We might be processing the block above the block that is in the player's way
						// Get the current row
						int currentRow = this.getRows().get(0);
						// How many rows are there between the player and the brick we're processing?
						int amountOfRowsBetweenBrickAndPlayer = currentRow - element.getRow();

						// Only set the player to the brick's coordinates if there's no more than one row between them
						// Or if there's no brick built in between the player and the brick
						if(amountOfRowsBetweenBrickAndPlayer <= 1 || !Element.isAnyElementAtPosition(currentRow - 1, element.getColumn()))
						{
							this.minimumYCoordinate = element.getMinimumYCoordinate() + Game.BRICK_WIDTH;
							this.maximumYCoordinate = element.getMinimumYCoordinate();
							
							checkIfPlayerMovedIntoExplosion();
						}
						
						// Otherwise, return so the rest of the function isn't called
						return;
					}
				}
			}
			
			// Nothing obstructing the player to move, set the coordinates to the newly calculated coordinates
			this.minimumYCoordinate = minY;
			this.maximumYCoordinate = maxY;
			
			checkIfPlayerMovedIntoExplosion();
		}
	}
	
	/**
	 * This method moves the player PLAYER_SPEED pixels downwards
	 */
	public void moveDown()
	{
		// Get the column or columns the player is located on
		int firstColumn = this.getColumns().get(0);
		int secondColumn = (this.getColumns().size() > 1) ? this.getColumns().get(1) : Game.INVALID_COLUMN_ID;
		
		// The player is trying to exit the play area
		if((this.maximumYCoordinate + speed) >= Game.LASTBRICK_MAX_Y_COORDINATE)
		{
			// Make sure there's no brick between the edge of the field and the player
			// He's only on one column
			if(secondColumn == Game.INVALID_COLUMN_ID)
			{
				if(Element.isAnyElementAtPosition(Game.AMOUNT_OF_ROWS - 1, firstColumn))
					return;
			}
			// He's on two columns
			else
			{
				if(Element.isAnyElementAtPosition(Game.AMOUNT_OF_ROWS - 1, firstColumn) || Element.isAnyElementAtPosition(Game.AMOUNT_OF_ROWS - 1, secondColumn))
					return;					
			}
			
			// No brick between the player and the edge, set the player at the edge
			this.maximumYCoordinate = Game.LASTBRICK_MAX_Y_COORDINATE;
			this.minimumYCoordinate = Game.LASTBRICK_MAX_Y_COORDINATE + Game.BRICK_WIDTH;
			
			checkIfPlayerMovedIntoExplosion();
		}
		else
		{
			// Calculate the position the player will be at once he has moved
			int minY = this.minimumYCoordinate + speed;
			int maxY = this.maximumYCoordinate + speed;
			
			// Loop through all the elements
			for(Element element : Element.getElementList())
			{
				if(element.getElementType() == Element.ElementType.TYPE_PLAYER)
					continue;
				
				if(element.getElementType() == Element.ElementType.TYPE_EXPLOSION)
					continue;
				
				// We only process the bricks that are on the same column or columns of the player
				if(element.getColumn() == firstColumn || (secondColumn != Game.INVALID_COLUMN_ID && element.getColumn() == secondColumn))
				{
					// Is the brick in the player's way?
					if(minY >= element.getMaximumYCoordinate() && minY <= element.getMinimumYCoordinate())
					{
						// We might be processing the block above the block that is in the player's way
						// Get the current row
						int currentRow = this.getRows().get(0);
						// How many rows are there between the player and the brick we're processing?
						int amountOfRowsBetweenBrickAndPlayer = element.getRow() - currentRow;

						// Only set the player to the brick's coordinates if there's no more than one row between them
						// Or if there's no brick built in between the player and the brick
						if(amountOfRowsBetweenBrickAndPlayer <= 1 || !Element.isAnyElementAtPosition(currentRow + 1, element.getColumn()))
						{
							if(!Element.isAnyElementAtPosition(element.getRow() - 1, element.getColumn()))
							{
								this.minimumYCoordinate = element.getMaximumYCoordinate();
								this.maximumYCoordinate = element.getMaximumYCoordinate() - Game.BRICK_WIDTH;
								
								checkIfPlayerMovedIntoExplosion();
							}
						}
						
						// Otherwise, return so the rest of the function isn't called
						return;
					}
				}
			}
			
			// Nothing obstructing the player to move, set the coordinates to the newly calculated coordinates
			this.minimumYCoordinate = minY;
			this.maximumYCoordinate = maxY;
			
			checkIfPlayerMovedIntoExplosion();
		}
	}
	
	/**
	 * This method moves the player PLAYER_SPEED pixels to the left
	 */
	public void moveToLeft()
	{
		// Get the row or rows the player is located on
		int firstRow = this.getRows().get(0);
		int secondRow = (this.getRows().size() > 1) ? this.getRows().get(1) : Game.INVALID_ROW_ID;
		
		// The player is trying to exit the play area
		if((this.minimumXCoordinate - speed) <= Game.FIRSTBRICK_MIN_X_COORDINATE)
		{
			// Make sure there's no brick between the edge of the field and the player
			// He's only one row
			if(secondRow == Game.INVALID_COLUMN_ID)
			{
				if(Element.isAnyElementAtPosition(firstRow, 0))
					return;
			}
			// He's on two rows
			else
			{
				if(Element.isAnyElementAtPosition(firstRow, 0) || Element.isAnyElementAtPosition(secondRow, 0))
					return;					
			}
			
			// No brick between the player and the edge, set the player at the edge
			this.maximumXCoordinate = Game.FIRSTBRICK_MIN_X_COORDINATE + Game.BRICK_WIDTH;
			this.minimumXCoordinate = Game.FIRSTBRICK_MIN_X_COORDINATE;
			
			checkIfPlayerMovedIntoExplosion();
		}
		// The player isn't trying to exit the play area
		else
		{
			// Calculate the position the player will be at once he has moved
			int minX = this.minimumXCoordinate - speed;
			int maxX = this.maximumXCoordinate - speed;
			
			// Loop through all the elements
			for(Element element : Element.getElementList())
			{
				if(element.getElementType() == Element.ElementType.TYPE_PLAYER)
					continue;
				
				if(element.getElementType() == Element.ElementType.TYPE_EXPLOSION)
					continue;
				
				// We only process the bricks that are on the same row or rows of the player
				if(element.getRow() == firstRow || (secondRow != Game.INVALID_ROW_ID && element.getRow() == secondRow))
				{
					// Is the brick in the player's way?
					if(minX <= element.getMaximumXCoordinate() && maxX >= element.getMaximumXCoordinate())
					{
						// We might be processing the block left to the block that is in the player's way
						// Get the current column
						int firstColumn = this.getColumns().get(0);
						int amountOfColumnsBetweenPlayerAndBrick = firstColumn - element.getColumn();

						// Only set the player to the brick's coordinates if there's no more than one column between them
						// Or if there's no brick built in between the player and the brick
						if(amountOfColumnsBetweenPlayerAndBrick <= 1 || !Element.isAnyElementAtPosition(firstRow, firstColumn - 1))
						{
							this.minimumXCoordinate = element.getMaximumXCoordinate();
							this.maximumXCoordinate = element.getMaximumXCoordinate() + Game.BRICK_WIDTH;

							checkIfPlayerMovedIntoExplosion();
						}
						
						// Otherwise, return so the rest of the function isn't called
						return;
					}
				}
			}
			
			// Otherwise, return so the rest of the function isn't called
			this.minimumXCoordinate = minX;
			this.maximumXCoordinate = maxX;

			checkIfPlayerMovedIntoExplosion();
		}	
	}
	
	/**
	 * This method moves the player PLAYER_SPEED pixels to the right
	 */
	public void moveToRight()
	{
		// Get the row or rows the player is located on
		int firstRow = this.getRows().get(0);
		int secondRow = (this.getRows().size() > 1) ? this.getRows().get(1) : Game.INVALID_ROW_ID;
		
		// The player is trying to exit the play area
		if((this.minimumXCoordinate + speed) >= Game.LASTBRICK_MIN_X_COORDINATE)
		{
			// Make sure there's no brick between the edge of the field and the player
			// He's only one row
			if(secondRow == Game.INVALID_COLUMN_ID)
			{
				if(Element.isAnyElementAtPosition(firstRow, Game.AMOUNT_OF_COLUMNS - 1))
					return;
			}
			// He's on two rows
			else
			{
				if(Element.isAnyElementAtPosition(firstRow, Game.AMOUNT_OF_COLUMNS - 1) || Element.isAnyElementAtPosition(secondRow, Game.AMOUNT_OF_COLUMNS - 1))
					return;					
			}
			
			// No brick between the player and the edge, set the player at the edge
			this.maximumXCoordinate = Game.LASTBRICK_MIN_X_COORDINATE + Game.BRICK_WIDTH + 1;
			this.minimumXCoordinate = Game.LASTBRICK_MIN_X_COORDINATE + 1;
			
			checkIfPlayerMovedIntoExplosion();
		}
		
		// The player isn't trying to exit the play area
		else
		{
			// Calculate the position the player will be at once he has moved
			int minX = this.minimumXCoordinate + speed;
			int maxX = this.maximumXCoordinate + speed;

			// Loop through all the elements
			for(Element element : Element.getElementList())
			{
				if(element.getElementType() == Element.ElementType.TYPE_PLAYER)
					continue;
				
				if(element.getElementType() == Element.ElementType.TYPE_EXPLOSION)
					continue;
				
				// We only process the bricks that are on the same row or rows of the player
				if(element.getRow() == firstRow || (secondRow != Game.INVALID_ROW_ID && element.getRow() == secondRow))
				{
					// Is the brick in the player's way?
					if(maxX >= element.getMinimumXCoordinate() && minX <= element.getMinimumXCoordinate())
					{
						// We might be processing the block left to the block that is in the player's way
						// Get the current column
						int firstColumn = this.getColumns().get(0);
						int amountOfColumnsBetweenPlayerAndBrick = element.getColumn() - firstColumn;

						// Only set the player to the brick's coordinates if there's no more than one column between them
						// Or if there's no brick built in between the player and the brick
						if(amountOfColumnsBetweenPlayerAndBrick <= 1 || !Element.isAnyElementAtPosition(firstRow, firstColumn + 1))
						{
							this.minimumXCoordinate = element.getMinimumXCoordinate() - Game.BRICK_WIDTH;
							this.maximumXCoordinate = element.getMinimumXCoordinate();
							
							checkIfPlayerMovedIntoExplosion();
						}
						
						// Otherwise, return so the rest of the function isn't called
						return;
					}
				}
			}
			
			// Otherwise, return so the rest of the function isn't called
			this.minimumXCoordinate = minX;
			this.maximumXCoordinate = maxX;
			
			checkIfPlayerMovedIntoExplosion();
		}
	}
	
	/**
	 * This method returns the row or rows the player is on. The player can be at 2 rows at most at the same time.
	 * 
	 * @return An arraylist containing integers representing the rows.
	 */
	public ArrayList<Integer> getRows()
	{
		// Create an array list to return
		ArrayList<Integer> returnArray = new ArrayList<Integer>();
		
		// Loop through all rows
		for(int row = 0; row < Game.AMOUNT_OF_ROWS; row++)
		{
			// Calculate the maxY and minY for each row
			int maxY = Game.FIRSTBRICK_MAX_Y_COORDINATE + (Game.BRICK_WIDTH * row) - 1;
			int minY = (Game.FIRSTBRICK_MAX_Y_COORDINATE + (Game.BRICK_WIDTH * (row + 1)) - 1);
			
			// If the coordinates match the player is on a single row
			if(this.minimumYCoordinate == minY && this.maximumYCoordinate == maxY)
			{
				returnArray.add(row);
				break;
			}
			
			// We're on two rows
			if((this.maximumYCoordinate - maxY) < Game.BRICK_WIDTH && (this.minimumYCoordinate - minY) < Game.BRICK_WIDTH)
			{
				returnArray.add(row);
				returnArray.add(row + 1);
				break;
			}
		}
		
		return returnArray;
	}
	
	/**
	 * This method returns the column or columns the player is on. The player can be at 2 columns at most at the same time.
	 * 
	 * @return An arraylist containing integers representing the columns.
	 */
	public ArrayList<Integer> getColumns()
	{
		// Create an array list to return
		ArrayList<Integer> returnArray = new ArrayList<Integer>();
		
		// Loop through all columns
		for(int column = 0; column < Game.AMOUNT_OF_COLUMNS; column++)
		{
			// Calculate the maxX and minX for each column
			int maxX = (Game.FIRSTBRICK_MIN_X_COORDINATE + (Game.BRICK_WIDTH * (column + 1)));
			int minX = Game.FIRSTBRICK_MIN_X_COORDINATE + (Game.BRICK_WIDTH * column);
			
			// If the coordinates match the player is on a single column
			if(this.maximumXCoordinate == maxX && this.minimumXCoordinate == minX)
			{
				returnArray.add(column);
				break;
			}
			
			// We're on two columns
			if((this.maximumXCoordinate - maxX) < Game.BRICK_WIDTH && (this.minimumXCoordinate - minX) < Game.BRICK_WIDTH)
			{
				returnArray.add(column);
				returnArray.add(column + 1);
				break;
			}
		}
		
		return returnArray;
	}
}